package hr.sting.droidorm;

import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import hr.sting.beans.PropertyInfo;
import hr.sting.droidorm.types.JavaToSQLTypeMapping;
import hr.sting.droidorm.types.JavaToSQLiteTypeMapping;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Transient;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

/**
 * @author mstipanov
 * @since 10.07.2010. 16:58:55
 */
public class DroidOrmManager {
    private static final String CREATE_TABLE_SQL =
            "CREATE TABLE [{0}] (\n" +
                    "   [{1}]    {2} PRIMARY KEY ASC\n " +
                    "  {3}" +
                    ");\n";

    private static final String ADD_COLUMN_SQL =
            "ALTER TABLE [{0}] ADD COLUMN [{1}] {2} {3} ";

    private static final String CREATE_TABLE_COLUMN_SQL =
            ", [{0}] {1} {2}\n ";

    private static final String TABLE_COLUMN_CONSTRAINT_SQL =
            "  CONSTRAINT {0}_{1}_NN NOT NULL";

    private SQLiteOpenHelper sqLiteOpenHelper;
    private JavaToSQLTypeMapping javaToSQLTypeMapping = new JavaToSQLiteTypeMapping();
    private Map<Class<?>, OrmEntityDescriptor<?>> descriptors = new HashMap<Class<?>, OrmEntityDescriptor<?>>();

    public void setSQLiteOpenHelper(SQLiteOpenHelper sqLiteOpenHelper) {
        this.sqLiteOpenHelper = sqLiteOpenHelper;
    }

    public void addEntity(Class<?> entityType) {
        descriptors.put(entityType, new OrmEntityDescriptor(entityType));
    }

    public void configure() {
        SQLiteDatabase db = sqLiteOpenHelper.getReadableDatabase();
        for (Class<?> aClass : descriptors.keySet()) {
            OrmEntityDescriptor<?> descriptor = descriptors.get(aClass);
            if (!columnExists(db, descriptor.getPrimaryKeyColumnName(), descriptor.getTableName())) {
                createTable(descriptor);
            }

            for (String column : descriptor.getColumnNames()) {
                if (!columnExists(db, column, descriptor.getTableName())) {
                    createColumn(descriptor, column);
                }
            }
        }
    }

    private boolean columnExists(SQLiteDatabase db, String idColumnName, String tableName) {
        try {
            db.query("[" + tableName + "]", new String[]{"[" + idColumnName + "]"}, "0 = 1", null, null, null, null);
            return true;
        } catch (SQLException e) {
            return false;
        }
    }

    private void createTable(OrmEntityDescriptor<?> descriptor) {
        SQLiteDatabase db = sqLiteOpenHelper.getWritableDatabase();
        StringBuilder columns = new StringBuilder();
        for (String column : descriptor.getColumnNames()) {
            PropertyInfo property = descriptor.getProperty(column);
            if (isId(property))
                continue;

            if (isTransient(property))
                continue;

            String constraint = getTableConstraint(descriptor, column, property);
            columns.append(MessageFormat.format(CREATE_TABLE_COLUMN_SQL, column, getSqlType(property.getPropertyType()), constraint));
        }
        String sql = MessageFormat.format(CREATE_TABLE_SQL, descriptor.getTableName(), descriptor.getPrimaryKeyColumnName(), getSqlType(descriptor.getProperty(descriptor.getPrimaryKeyColumnName()).getPropertyType()), columns);
        db.execSQL(sql);
    }

    private String getTableConstraint(OrmEntityDescriptor<?> descriptor, String column, PropertyInfo property) {
        String constraint = "";
        if (!isNullable(property))
            constraint = MessageFormat.format(TABLE_COLUMN_CONSTRAINT_SQL, descriptor.getTableName(), column);
        return constraint;
    }

    private void createColumn(OrmEntityDescriptor<?> descriptor, String column) {
        PropertyInfo property = descriptor.getProperty(column);
        if (isId(property))
            return;

        if (isTransient(property))
            return;

        SQLiteDatabase db = sqLiteOpenHelper.getWritableDatabase();
        String constraint = getTableConstraint(descriptor, column, property);
        String sql = MessageFormat.format(ADD_COLUMN_SQL, descriptor.getTableName(), column, getSqlType(property.getPropertyType()), constraint);
        db.execSQL(sql);
    }

    private boolean isId(PropertyInfo property) {
        Id id = property.getAnnotation(Id.class);
        return null != id;
    }

    private boolean isTransient(PropertyInfo property) {
        Transient aTransient = property.getAnnotation(Transient.class);
        return null != aTransient;
    }

    private boolean isNullable(PropertyInfo property) {
        Column column = property.getAnnotation(Column.class);
        return null == column || column.nullable();
    }

    private String getSqlType(Class<?> propertyType) {
        return javaToSQLTypeMapping.getSQLType(propertyType);
    }

    public <T> OrmEntityDescriptor<T> getOrmEntityDescriptor(Class<T> entityType) {
        return (OrmEntityDescriptor<T>) descriptors.get(entityType);
    }

    public SQLiteOpenHelper getSqLiteOpenHelper() {
        return sqLiteOpenHelper;
    }
}
